﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using PB.Airsoft.Business.Utilities.Caching;
using PB.Airsoft.Core.Utilities.Validation;
using System.Web;
using System.Web.Caching;
using System.Collections;

namespace PB.Airsoft.Web.Business.Utilities.Caching
{
    public class WebCacheManager : CacheManager
    {
        private const string KeySeparator = ":::";

        public const int DefaultCacheMinutes = 60;

        private string MakeKey(string key, string category)
        {
            return String.Concat(category, KeySeparator, key);
        }
        
        #region AddToCache

        public override void AddToCache(string key, string category, object data)
        {
            AddToCache(key, category, data, DefaultCacheMinutes);
        }

        public override void AddToCache(string key, string category, object data, int minutesToCache)
        {
            AddToCache(key, category, data, minutesToCache, CacheItemPriority.Normal);
        }

        public void AddToCache(string key, string category, object data, int minutesToCache, CacheItemPriority priority)
        {
            Guard.ThrowIfEmptyString(key, "key", "Key cannot be empty.");
            Guard.ThrowIfEmptyString(category, "category", "Category cannot be empty.");

            lock (HttpRuntime.Cache)
            {
                string fullKey = MakeKey(key, category);
                Cache cache = HttpRuntime.Cache;
                cache.Remove(fullKey);
                cache.Add(fullKey, data, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(minutesToCache), priority, null);
            }
        }

        #endregion

        #region GetFromCache

        public override object GetFromCache(string key, string category)
        {
            Guard.ThrowIfEmptyString(key, "key", "Cache key cannot be null or empty.");
            Guard.ThrowIfEmptyString(category, "category", "Cache category cannot be null or empty.");

            lock (HttpRuntime.Cache)
            {
                string fullKey = MakeKey(key, category);
                return HttpRuntime.Cache[fullKey];
            }
        }

        #endregion

        #region RemoveFromCache

        public override void RemoveFromCache(string key, string category)
        {
            Guard.ThrowIfEmptyString(key, "key", "Cache key cannot be null or empty.");
            Guard.ThrowIfEmptyString(category, "category", "Cache category cannot be null or empty.");

            lock (HttpRuntime.Cache)
            {
                string fullKey = MakeKey(key, category);
                HttpRuntime.Cache.Remove(fullKey);
            }
        }

        #endregion

        #region ClearCache

        public override void ClearCache()
        {
            RemoveCacheItems(key => true);
        }

        public override void ClearCache(string category)
        {
            string start = category + KeySeparator;
            RemoveCacheItems(key => key.StartsWith(start));
        }

        private void RemoveCacheItems(Func<string, bool> keyPredicate)
        {
            lock (HttpRuntime.Cache)
            {
                Cache cache = HttpRuntime.Cache;
                var keysToRemove = from entry in cache.Cast<DictionaryEntry>()
                                   where keyPredicate(entry.Key.ToString())
                                   select entry.Key.ToString();
                foreach (var key in keysToRemove)
                {
                    cache.Remove(key);
                }
            }
        }

        #endregion

        #region FromCache

        public override T FromCache<T>(string key, string category, int minutesToCache, Func<T> actionFunction)
        {
            Guard.ThrowIfEmptyString(key, "The supplied key is invalid.");
            Guard.ThrowIfLessThan(minutesToCache, 0, "The supplied minutes to cache cannot be below zero", "minutesToCache");

            //Check if result can be found in the current cache
            object result = GetFromCache(key, category);
            if (result != null && (result.GetType() == typeof(T) || typeof(T).IsAssignableFrom(result.GetType())))
                return (T)result;

            //Execute the delegate function
            T returnValue;
            returnValue = actionFunction();

            //Add the results to the cache
            if (returnValue != null)
                AddToCache(key, category, returnValue, minutesToCache);

            //Return the newly fetched result
            return returnValue;
        }

        #endregion
    }
}
